package com.dream.file;

import com.dream.file.chunk.ChunkUploadResult;
import com.dream.file.chunk.UploadChunkRepository;
import com.dream.file.chunk.UploadTaskRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.model.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Amazon S3文件存储实现
 * 基于AWS S3对象存储服务实现文件存储、上传、下载等功能
 */
@Slf4j
public class S3FileStorage implements FileStorage {

    /**
     * AWS S3客户端
     */
    private final S3Client s3Client;
    
    /**
     * 存储配置信息
     */
    private final StorageConfig config;
    
    /**
     * 上传任务仓库
     */
    private final UploadTaskRepository uploadTaskRepository;
    
    /**
     * 上传分片仓库
     */
    private final UploadChunkRepository uploadChunkRepository;
    
    /**
     * 线程池，用于异步处理任务
     */
    private final ExecutorService executorService;

    /**
     * 构造方法
     * 
     * @param config 存储配置
     * @param uploadTaskRepository 上传任务仓库
     * @param uploadChunkRepository 上传分片仓库
     */
    public S3FileStorage(StorageConfig config,
                         UploadTaskRepository uploadTaskRepository,
                         UploadChunkRepository uploadChunkRepository) {
        this.config = config;
        
        try {
            AwsBasicCredentials credentials = AwsBasicCredentials.create(
                    config.getAssessKey(), 
                    config.getSecretKey());
            
            // 构建S3客户端
            S3ClientBuilder builder = S3Client.builder()
                    .credentialsProvider(StaticCredentialsProvider.create(credentials));
            
            // 如果是自定义endpoint（比如兼容S3的其他服务）
            if (config.getEndpoint() != null && !config.getEndpoint().isEmpty()) {
                builder.endpointOverride(URI.create(config.getEndpoint()));
            } else {
                // 使用默认区域，实际应用中应从配置中获取
                builder.region(Region.US_EAST_1);
            }
            
            this.s3Client = builder.build();
            
            // 确保存储桶存在
            createBucketIfNotExists();
        } catch (Exception e) {
            throw new FileStorageException("初始化S3客户端失败", e);
        }
        
        this.uploadTaskRepository = uploadTaskRepository;
        this.uploadChunkRepository = uploadChunkRepository;
        this.executorService = Executors.newFixedThreadPool(10);
    }

    /**
     * 如果存储桶不存在则创建
     */
    private void createBucketIfNotExists() {
        try {
            // 检查存储桶是否存在
            boolean bucketExists = false;
            try {
                s3Client.headBucket(HeadBucketRequest.builder()
                        .bucket(config.getBucketName())
                        .build());
                bucketExists = true;
            } catch (NoSuchBucketException e) {
                // 桶不存在，需要创建
                bucketExists = false;
            }
            
            if (!bucketExists) {
                s3Client.createBucket(CreateBucketRequest.builder()
                        .bucket(config.getBucketName())
                        .build());
                log.info("创建S3存储桶成功: {}", config.getBucketName());
            }
        } catch (Exception e) {
            throw new FileStorageException("创建或检查S3存储桶失败", e);
        }
    }

    /**
     * 上传文件
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @return 访问URL
     */
    @Override
    public String upload(InputStream inputStream, String path, String fileName) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            
            // 读取inputStream内容到字节数组
            byte[] bytes = inputStream.readAllBytes();
            
            // 设置内容类型
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .contentType(getContentType(fileName))
                    .build();
            
            // 上传文件
            s3Client.putObject(putObjectRequest, RequestBody.fromBytes(bytes));
            
            log.info("文件上传到S3成功: {}", objectKey);
            return generateUrl(objectKey);
        } catch (Exception e) {
            throw new FileStorageException("文件上传失败", e);
        }
    }

    /**
     * 分片上传文件
     * 支持大文件的分片上传，可以实现断点续传
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @param chunkNumber 当前分片序号
     * @param totalChunks 总分片数
     * @return 分片上传结果
     */
    @Override
    public ChunkUploadResult uploadChunk(InputStream inputStream, String path, String fileName,
                                         int chunkNumber, int totalChunks) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            String uploadId = getOrCreateMultipartUploadId(path, fileName);
            
            // 读取分片内容
            byte[] bytes = inputStream.readAllBytes();
            
            // 上传分片
            UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .uploadId(uploadId)
                    .partNumber(chunkNumber)
                    .build();
            
            UploadPartResponse uploadPartResponse = s3Client.uploadPart(
                    uploadPartRequest,
                    RequestBody.fromBytes(bytes)
            );
            
            // 创建结果对象
            ChunkUploadResult result = new ChunkUploadResult();
            result.setUploadId(uploadId);
            result.setEtag(uploadPartResponse.eTag());
            result.setPartNumber(chunkNumber);
            result.setCompleted(chunkNumber == totalChunks);
            
            // 如果是最后一个分片，执行合并操作
            if (result.isCompleted()) {
                completeMultipartUpload(objectKey, uploadId, totalChunks);
            }
            
            return result;
        } catch (Exception e) {
            throw new FileStorageException("分片上传失败", e);
        }
    }

    /**
     * 下载文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件输入流
     */
    @Override
    public InputStream download(String path, String fileName) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .build();
            
            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            
            // 注意：实际应用中应考虑如何管理此输入流，确保资源被正确关闭
            return response;
        } catch (Exception e) {
            throw new FileStorageException("文件下载失败", e);
        }
    }

    /**
     * 范围下载文件
     * 支持断点续传下载，指定下载文件的部分内容
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @param start 开始位置（字节偏移量）
     * @param end 结束位置（字节偏移量）
     * @return 文件部分内容的输入流
     */
    @Override
    public InputStream downloadRange(String path, String fileName, long start, long end) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            
            // 设置范围请求
            String range = String.format("bytes=%d-%d", start, end);
            
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .range(range)
                    .build();
            
            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            
            // 同样需要注意资源管理
            return response;
        } catch (Exception e) {
            throw new FileStorageException("范围下载失败", e);
        }
    }

    /**
     * 删除文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 是否删除成功
     */
    @Override
    public boolean delete(String path, String fileName) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            
            DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .build();
            
            s3Client.deleteObject(deleteObjectRequest);
            log.info("文件从S3删除成功: {}", objectKey);
            return true;
        } catch (Exception e) {
            log.error("文件删除失败", e);
            return false;
        }
    }

    /**
     * 获取文件元数据信息
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件元数据
     */
    @Override
    public FileMetadata getMetadata(String path, String fileName) {
        try {
            String objectKey = buildObjectKey(path, fileName);
            
            HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .build();
            
            HeadObjectResponse headObjectResponse = s3Client.headObject(headObjectRequest);
            
            FileMetadata metadata = new FileMetadata();
            metadata.setFileName(fileName);
            metadata.setPath(path);
            metadata.setSize(headObjectResponse.contentLength());
            metadata.setContentType(headObjectResponse.contentType());
            metadata.setLastModified(headObjectResponse.lastModified());
            metadata.setEtag(headObjectResponse.eTag());
            
            // 获取自定义元数据
            Map<String, String> metaMap = new HashMap<>();
            headObjectResponse.metadata().forEach((key, value) -> {
                // S3自定义元数据以x-amz-meta-为前缀
                if (key.startsWith("x-amz-meta-")) {
                    String metaKey = key.substring("x-amz-meta-".length());
                    metaMap.put(metaKey, value);
                }
            });
            metadata.setMetadata(metaMap);
            
            return metadata;
        } catch (Exception e) {
            throw new FileStorageException("获取文件元数据失败", e);
        }
    }

    /**
     * 构建对象键
     * 将路径和文件名组合成S3对象键
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 完整的对象键
     */
    private String buildObjectKey(String path, String fileName) {
        String basePath = StringUtils.trimToEmpty(config.getBasePath());
        path = StringUtils.trimToEmpty(path);
        return String.format("%s/%s/%s", basePath, path, fileName)
                .replaceAll("//+", "/")
                .replaceAll("^/", "");
    }

    /**
     * 生成访问URL
     * 
     * @param objectKey 对象键
     * @return 访问URL
     */
    private String generateUrl(String objectKey) {
        // 简单实现，实际应用中可能需要预签名URL或其他策略
        return String.format("%s/%s/%s",
                config.getEndpoint().endsWith("/") ? config.getEndpoint().substring(0, config.getEndpoint().length() - 1) : config.getEndpoint(),
                config.getBucketName(),
                objectKey);
    }

    /**
     * 获取文件内容类型
     * 
     * @param fileName 文件名
     * @return 内容类型/MIME类型
     */
    private String getContentType(String fileName) {
        // 简单实现，实际项目中可以使用更完善的方法
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        return switch (ext) {
            case "jpg", "jpeg" -> "image/jpeg";
            case "png" -> "image/png";
            case "pdf" -> "application/pdf";
            default -> "application/octet-stream";
        };
    }

    /**
     * 获取或创建分片上传ID
     * 
     * @param path 存储路径
     * @param fileName 文件名
     * @return 上传ID
     */
    private String getOrCreateMultipartUploadId(String path, String fileName) {
        // 在实际项目中，应从数据库中获取已有的uploadId
        // 这里简单实现为创建新的上传ID
        String objectKey = buildObjectKey(path, fileName);
        
        CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest.builder()
                .bucket(config.getBucketName())
                .key(objectKey)
                .contentType(getContentType(fileName))
                .build();
        
        CreateMultipartUploadResponse response = s3Client.createMultipartUpload(createMultipartUploadRequest);
        return response.uploadId();
    }

    /**
     * 完成分片上传，合并文件
     * 
     * @param objectKey 对象键
     * @param uploadId 上传ID
     * @param totalChunks 总分片数
     */
    private void completeMultipartUpload(String objectKey, String uploadId, int totalChunks) {
        try {
            // 收集所有分片信息
            List<CompletedPart> completedParts = new ArrayList<>();
            
            for (int i = 1; i <= totalChunks; i++) {
                // 在实际应用中，这里应该从数据库获取每个分片的ETag
                // 这里简化实现，通过HeadPart API获取
                ListPartsRequest listPartsRequest = ListPartsRequest.builder()
                        .bucket(config.getBucketName())
                        .key(objectKey)
                        .uploadId(uploadId)
                        .build();
                
                ListPartsResponse listPartsResponse = s3Client.listParts(listPartsRequest);
                
                listPartsResponse.parts().forEach(part -> {
                    completedParts.add(CompletedPart.builder()
                            .partNumber(part.partNumber())
                            .eTag(part.eTag())
                            .build());
                });
            }
            
            // 完成分片上传
            CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
                    .parts(completedParts)
                    .build();
            
            CompleteMultipartUploadRequest completeMultipartUploadRequest = CompleteMultipartUploadRequest.builder()
                    .bucket(config.getBucketName())
                    .key(objectKey)
                    .uploadId(uploadId)
                    .multipartUpload(completedMultipartUpload)
                    .build();
            
            s3Client.completeMultipartUpload(completeMultipartUploadRequest);
            
            log.info("分片合并完成: {}", objectKey);
        } catch (Exception e) {
            // 如果合并失败，应该中止分片上传并清理资源
            try {
                AbortMultipartUploadRequest abortMultipartUploadRequest = AbortMultipartUploadRequest.builder()
                        .bucket(config.getBucketName())
                        .key(objectKey)
                        .uploadId(uploadId)
                        .build();
                
                s3Client.abortMultipartUpload(abortMultipartUploadRequest);
                log.warn("分片合并失败，已中止上传: {}", objectKey);
            } catch (Exception abortException) {
                log.error("中止分片上传失败", abortException);
            }
            
            throw new FileStorageException("合并分片失败", e);
        }
    }
} 